/*
 * 代号：凤凰
 * http://www.jphenix.org
 * 2020年7月31日
 * V4.0
 */
package com.jphenix.servlet.filter;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.FilterConfig;

import com.jphenix.driver.nodehandler.FNodeHandler;
import com.jphenix.kernel.classloader.ResourcesLoader;
import com.jphenix.kernel.objectloader.util.JarResourceVO;
import com.jphenix.servlet.common.HttpServletRequestImpl;
import com.jphenix.servlet.parent.AdminBeanParent;
import com.jphenix.servlet.parent.ServiceBeanParent;
import com.jphenix.share.lang.SDate;
import com.jphenix.share.tools.Des;
import com.jphenix.share.tools.FileCopyTools;
import com.jphenix.share.tools.HttpCall;
import com.jphenix.share.tools.MD5;
import com.jphenix.share.util.BaseUtil;
import com.jphenix.share.util.SFilesUtil;
import com.jphenix.standard.docs.BeanInfo;
import com.jphenix.standard.docs.ClassInfo;
import com.jphenix.standard.docs.Running;
import com.jphenix.standard.servlet.IActionContext;
import com.jphenix.standard.servlet.IBytesFilter;
import com.jphenix.standard.servlet.IFilter;
import com.jphenix.standard.servlet.IRequest;
import com.jphenix.standard.servlet.IResponse;
import com.jphenix.standard.viewhandler.IViewHandler;

/**
 * 开发控制台响应过滤器
 * com.jphenix.servlet.filter.ConsoleFilter
 * 
 * 特点：提高了开发人员访问控制台的安全度。
 * 
 * 安全方面：
 * 
 * 	1. 控制台的请求入口归纳成一个，以前是很多个。
 *  2. 控制台的请求入口每天都不一样，并且很难通过暴力方式猜到。
 *  3. 验证授权码后，并不是简单的在会话中标记验证通过，这样容易通过恶意代码直接标记验证通过状态。
 *     目前验证通过的会话状态值的主键，每天都是变化的，并且很难猜到。
 *  4. AdminBeanParent 管理台程序父类中，没有逻辑验证代码，避免被继承修改，而是在父类中调用当前类实例做判断。
 *  5. 页面中所有请求资源都似乎加密的，并且请求入口只有一个，方便做验证。避免出现其它处的漏洞。
 *  6. 取消了数据库授权码，很多年下来，只有一个开发商，没必要从数据库中获取授权码。
 *  
 * ------------------------------------------------------------------------------------------------------------
 * 
 *   验证模式分为两种：
 *   
 *   1. 内核控制台访问控制（强加密，可以暴露在外网）
 *   2. 富管理台（用户后台管理中包含了开发平台）访问控制（普通加密，不建议暴露在外网）
 *      这种加密模式仅验证授权码，并不加密请求路径。
 *      采用这种验证模式，需要在配置文件中增加属性 console_verify_normal=“1”
 *   3. 如果在服务器本机用http://localhost/虚拟路径/_dev_console.ha 访问，则不需要配置safe_link信息（授权码信息），可以直接访问
 *      
 * ------------------------------------------------------------------------------------------------------------
 * 
 *   配置文件格式：
 *   
 *     单一授权码：（最常用模式）
 *     
 *     <safe_link 
 *        disabled="是否禁用 0/1"
 *        value="授权码比对值" 
 *        content_not_from_jar=“/是否不从jar文件中获取资源（从实际文件夹中获取，通常用来调试jar包） 1/0” 
 *        dev_code="开发商代码“ 
 *        dev_name="开发商名称" 
 *        console_verify_normal="是否采用普通验证方式 1/0"/>
 *     
 *     
 *     该模式通常用在jar内置管理台操作，无console_verify_normal属性，或者该值为0，控制台入口只有一个并且每天都不同，所有请求路径都是加密的。
 *     
 *     如果console_verify_normal属性值为1，仅作授权码验证判断是否具备访问权限。所有请求路径都是明文不加密
 *     
 *     ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 *     
 *     多授权码：（多开发商模式，该模式仅支持富管理台上开发，仅支持普通验证模式，默认该模式）
 *     
 *     <safe_link vlaue="默认授权码对比值，该模式中也必须存在这个值” content_not_from_jar="0/1" disabled=" 0/1">
 *       <dev value="授权码比对值" dev_code="开发商代码“ dev_name="开发商名称" />
 *       <dev value="授权码比对值" dev_code="开发商代码“ dev_name="开发商名称" />
 *     </safe_link>
 *     
 *     该模式默认工作在普通验证方式，即只作授权码验证，请求路径都是明文不加密的。
 *     但是在该模式下，可以通过safe_link节点中value默认授权码对比值做jar内置管理台登录操作。
 *     
 *     注意：多授权码模式中，也必须要有safe_link节点中value默认授权码值
 *     
 *     在多授权码模式中，验证成功后，会在会话中保存配置文件中对应该供应商节点的属性信息Map<String,String> 会话主键：_dev_info 
 * 
 * ------------------------------------------------------------------------------------------------------------
 * 
 * 请求路径是：  配置文件中 md5(safe_link/value 的值+当前日期)
 * 
 * ------------------------------------------------------------------------------------------------------------
 * 
 * 
 * 内部控制台页面开发时，路径格式为：
 * 
 *  *{jar包中相对于/resources/web/adm的文件相对路径（路径开头需要为/）}
 * 比如：<a href="*{/editor/index.htm}&id=000001">go</a>
 *     
 * 输出的代码格式为：<a href="/[虚拟路径]/1586727D8EBB199DA32E9BC6AD15519D.ha?303361ECE16A552209743637BF555059&id=000001">go</a>
 * 
 *   注意： 1. 加  *{} 符号的目的是把内部的路径转换成加密的字符串，仅针对文件路径加密，不要包含参数
 *          2. 如果路径需要加参数，文件路径末尾并不是问号，而是&符号，因为转义成加密字符后，本身带了问号
 *          3. 关于虚拟路径，除了servlet容器中配置的虚拟路径，还可以是多级路径，比如 /aaa/bbb  等任意路径，系统都能响应得到。
 *             目的是为了避免默认路径被防火墙拦截，导致无法访问管理控制台。
 *          4. 如果是访问动态模板页面（扩展名为.htm），请求url只能是.htm 不能写成 .ha
 *          5. 这种格式仅针对内部管理控制台相关页面（放在jar包中的页面）开发。
 *    
 *          
 *  另外还有一种写法，比如：var url = "*{}/_log.ha";   输出到页面中为： var url = "/[虚拟路径]/_log.ha";
 *  
 *      因为_log.ha 并不是实际页面或者对应的脚本，而是一个过滤器元素，而且优先级高于管理台过滤器，也就是说，如果给这个路径做加密，加密后的请求
 *  路径先经过对应的请求过滤器，然后才经过控制台过滤器做路径解密，导致对应的过滤器无法响应加密的动作路径，所以不能对这类路径做加密。
 *  
 *     *{} 括号内部是空的，仅这三个字符，在输出时，会自动替换为当前请求虚拟路径。
 *          
 * ------------------------------------------------------------------------------------------------------------
 * 
 * 2020-08-16 修改了解析htm中的宏代码错误
 * 2020-08-31 增加了取消当前授权码验证状态方法
 * 2020-09-08 增加了获取jar包内部文件字节数组
 * 2020-09-18 改造了加解密类，将该类以类实例方式常驻内存提高效率
 * 2021-03-17 除去了废弃的引用代码，适配新的注册中心
 * 2021-09-12 增加了在服务器本机访问开发控制台，无需输入授权码，可以直接登入
 * 
 * @author MBG
 * 2020年7月31日
 */
@Running({"80"})
@ClassInfo({"2021-09-12 11:22","开发控制台响应过滤器"})
@BeanInfo({"consolefilter"})
public class ConsoleFilter extends ServiceBeanParent implements IFilter {

	private boolean            enabled        = false;     // 是否允许控制台响应
	private boolean            notFromJar     = false;     // 是否不从jar文件中获取资源（从实际文件夹中获取，通常用来调试jar包）
	private boolean            normalVerfy    = false;     // 开发平台是否采用普通验证方式
	private String             actionPath     = null;      // 响应路径
	private String             nowDay         = null;      // 当前日期
	private Map<String,String> mappingMap     = null;      // 虚拟路径映射对照容器
	private List<String>       mappingKeyList = null;      // 虚拟路径主键序列
	private ResourcesLoader    rl             = null;      // 资源加载器
	private PageFilter         pf             = null;      // 页面（模板）请求过滤器
    private List<IBytesFilter> bfs            = null;      // 字节处理过滤器
    private Des                des            = new Des(); // 加解密处理类
	
	/**
	 * 构造函数
	 * @author MBG
	 */
	public ConsoleFilter() {
		super();
	}

	/**
	 * 执行初始化
	 * 放到最后再执行
	 * @param fe         过滤器管理类
	 * @param config     Servlet配置信息类
	 * @throws Exception 异常（如果初始化发生异常，则放弃不再使用）
	 * 2019年6月15日
	 * @author MBG
	 */
	@Override
	public int getIndex() {
		return 50;
	}

	/**
	 * 获取需要过滤的动作路径扩展名
	 * 多个扩展名用逗号分割
	 * 多个扩展名用半角逗号分割
	 * 扩展名前不用加半角聚号（.)
	 * 如果返回空，或者空字符串，说明需要过滤全部动作路径（不建议这么做）
	 * 如果需要过滤无扩展名的动作，用半角减号（-）标记
	 * @return 需要过滤的动作路径扩展名
	 * 2014年9月12日
	 * @author 马宝刚
	 */
	@Override
	public String getFilterActionExtName() {
		return "ha";
	}

	/**
	 * 执行过滤
	 * 刘虻
	 * 2010-5-25 下午04:09:25
	 * @param req 页面请求
	 * @param resp 页面反馈
	 * @return 如果返回真，则不继续往下进行，直接跳过结束
	 * @throws Exception 执行发生异常
	 */
	@Override
	public boolean doFilter(IRequest req, IResponse resp) throws Exception {
		String servletPath; //请求路径
		String removeAddr = req.getRemoteAddr(); //客户端IP地址
		if("0:0:0:0:0:0:0:1".equals(removeAddr) || "127.0.0.1".equals(removeAddr)) {
			//本地请求，无需输入授权码
			servletPath = req.getServletPath();
			actionPath = MD5.md5("local"+SDate.nowDateString()).toUpperCase()+".ha";
			if("/_dev_console.ha".equals(servletPath)) {
				req.getSession().setAttribute(actionPath,"1");
				resp.sendRedirect(req.getContextPath()+"/"+actionPath);
				return true;
			}else if("1".equals(req.getSession().getAttribute(actionPath)) && servletPath.endsWith(actionPath)) {
				des.setKey(actionPath); //设置秘钥
			} else {
				return false;
			}
		} else {
			if(!enabled) {
				return false;
			}
			String now = SDate.nowDateString(); //当前日期
			if(!now.equals(nowDay)) {
				//过了当天，需要重新初始化路径
				if(!initActionName()) {
					return false;
				}
			}
			//动作路径
			servletPath = req.getServletPath();
			if(!servletPath.endsWith(actionPath)) {
				return false;
			}
			if(!boo(req.getSession().getAttribute(actionPath))) {
				String value = pr("safe_link","value"); //配置文件中的MD5值
				if(value.length()<1) {
					info("ConsoleFilter Not Find The Attribute [value] from PropertyFile nodeName:safe_link,enabled=false");
					enabled = false;
					return false;
				}
				//获取授权码
				String tokenSrc = req.getParameter("token");
				String token    = MD5.getMD5Value(tokenSrc);
				if(token.length()<1 || !token.equals(value)) {
					info("控制台验证失败：提交值:["+tokenSrc+"] MD5提交值:["+token+"] 默认值:["+value+"]");
					return false;
				}
				//标记验证成功
				req.getSession().setAttribute(actionPath,"1");
			}
		}
		
		//请求参数
		String queryString = req.getQueryString();
		int    point       = queryString.indexOf("&");
		String action      = null;  //实际请求路径
		String extName     = null;  //扩展名
		if(point>0) {
			action         = queryString.substring(0,point);
			queryString    = queryString.substring(point+1);
		}else {
			action         = queryString;
		}
		if(action.length()>0) {
			action = des.decode(action);
			point  = action.indexOf(".");
			if(point>0) {
				extName = action.substring(point+1).toLowerCase();
			}else {
				extName = "";
			}
		}else {
			action  = "/index.htm";
			extName = "htm";
		}
		byte[] cnt; //待输出的内容
		if("ha".equals(extName)) {
			((HttpServletRequestImpl)req).setServletPath(action);
			return false;
		}else if("htm".equals(extName)
				|| "html".equals(extName)
				|| "js".equals(extName)
				|| "css".equals(extName)) {
			//获取内部文件内容
			cnt = serveInsideFile(action,resp,true);
			if(cnt==null) {
				return true;
			}
			if("html".equals(extName) || "htm".equals(extName)) {
				if(bfs!=null && bfs.size()>0) {
					for(IBytesFilter bf:bfs) {
						cnt = bf.doBytesFilter(cnt,req,resp);
					}
				}
				if("htm".equals(extName)) {
					//构造视图对象
					IViewHandler vh = FNodeHandler.newBody(cnt);
					//执行解析视图对象
					pf.parseNode(action,req,resp,vh);
					//转义路径信息
					cnt = fixContentPath(vh.getNodeBody().getBytes("UTF-8"),req.getContextPath()+servletPath);
				}else {
					//转义路径信息
					cnt = fixContentPath(cnt,req.getContextPath()+servletPath);
				}
			}else {
				//转义路径信息
				cnt = fixContentPath(cnt,req.getContextPath()+servletPath);
			}
			resp.setHeader("Content-Length", Long.toString(cnt.length));
			OutputStream os = null; //输出流
			try {
				os = resp.getOutputStream();
				os.write(cnt);
			}finally {
				try {
					os.flush();
				}catch(Exception e) {}
				try {
					os.close();
				}catch(Exception e) {}
				os = null;
			}
		}else {
			//输出内部非文本文件
			serveInsideFile(action,resp,false);
		}
		return true;
	}
	
	/**
	 * 取消当前授权码验证状态（随后需要重新验证）
	 * @param ac        动作上下文
	 * 2020年8月31日
	 * @author MBG
	 */
	public void cancelSign(IActionContext ac) {
		if(ac==null) {
			return;
		}
		ac.getRequest().getSession().removeAttribute(actionPath);
	}
	
	
	/**
	 * 验证是否授权成功
	 * @param ac         动作上下文
	 * @return           是否授权成功
	 * @throws Exception 异常
	 * 2020年8月2日
	 * @author MBG
	 */
	public boolean verify(IActionContext ac,Object invoker) throws Exception{
		if(invoker==null 
				|| !(invoker instanceof AdminBeanParent)) {
			ac.getResponse().sendError(403);
			return false;
		}
		if(!boo(ac.getRequest().getSession().getAttribute(actionPath))) {
			if(normalVerfy) {
				//获取授权码
				String token = MD5.getMD5Value((ac.getParameter("token")));
				if(token.length()<1) {
					ac.getResponse().sendError(403);
					return false;
				}
				//配置信息块
				IViewHandler       pxml = px("safe_link");
				//供应商授权码信息对象序列
				List<IViewHandler> eles = pxml.getChildNodesByNodeName("dev");
				if(eles.size()<1) {
					//单一授权码，普通验证模式
					if(token.equals(pxml.a("value"))) {
						ac.getRequest().getSession().setAttribute(actionPath,"1");
						
						//获取全部属性值
						Map<String,String> devMap = new HashMap<>();
						devMap.putAll(pxml.am());
						devMap.remove("value");
						devMap.remove("disabled");
						devMap.remove("content_not_from_jar");
						devMap.remove("console_verify_normal");
						ac.getRequest().getSession().setAttribute("_dev_info",devMap);
						return true;
					}
				}else {
					//多授权码，普通验证模式（多授权码不存在强验证模式）
					for(IViewHandler ele:eles) {
						if(token.equals(ele.a("value"))) {
							ac.getRequest().getSession().setAttribute(actionPath,"1");
							
							//获取全部属性值
							Map<String,String> devMap = new HashMap<>();
							devMap.putAll(ele.am());
							devMap.remove("value");
							ac.getRequest().getSession().setAttribute("_dev_info",devMap);
							return true;
						}
					}
				}
				info("控制台验证失败：提交值:["+token+"]");
			}
			ac.getResponse().sendError(403);
			return false;
		}
		return true;
	}

	/**
	 * 执行初始化
	 * @param fe         过滤器管理类
	 * @param config     Servlet配置信息类
	 * @throws Exception 异常（如果初始化发生异常，则放弃不再使用）
	 * 2019年6月15日
	 * @author MBG
	 */
	@Override
	public void init(FilterExplorer fe, FilterConfig config) throws Exception {
		//获取指定配置信息块
		IViewHandler pxml = px("safe_link");
		if(boo(pxml.a("disabled"))) {
			enabled = false;
		}
		//初始化动作路径
		initActionName();
		
		pf             = bean(PageFilter.class);
		// 判断页面文件是否存在于jar包中，主要看当前类的根路径是否在 /WEB-INF/lib中，如果在这里，肯定在jar包中
		String classPath = SFilesUtil.getBaseClassPath(this.getClass());
		if(classPath!=null && !classPath.endsWith("/WEB-INF/lib/")) {
			notFromJar = true;
		}else {
			//是否不从jar包中读取文本文件（从实际文件夹读取，通常用在调试jar包）
			notFromJar     = boo(pxml.a("content_not_from_jar"));
		}
		
		normalVerfy    = boo(pxml.a("console_verify_normal"));
		if(!normalVerfy) {
			//如果存在多个开发商授权码，标记为普通模式
			if(pxml.hasChildNodeByNodeName("dev")) {
				normalVerfy = true;
			}
		}
		bfs            = fe.getByteFilterList();
		//初始化内部资源映射
		rl             = bean(ResourcesLoader.class);
		mappingMap     = rl.getPropertyMap("mapping");
		mappingKeyList = BaseUtil.getMapKeyList(mappingMap);
		
		log.startLog("---初始化当前控制台请求路径完毕--- 是否强验证模式：["+!normalVerfy+"]");
	}
	
	/**
	 * 初始化响应动作路径
	 * @return 初始化动作路径成功
	 * 2020年7月31日
	 * @author MBG
	 */
	private boolean initActionName() {
		//授权码md5值
		String safeCode = pr("safe_link","value");
		if(safeCode.length()<1) {
			enabled = false;
			return false;
		}
		enabled = true;
		//设置当前日期
		nowDay     = SDate.nowDateString();
		//请求路径
		actionPath = MD5.md5(safeCode+nowDay+safeCode).toUpperCase()+".ha";
    
		des.setKey(actionPath); //设置秘钥

		//没必要在日志里显示秘钥，直接查看配置文件即可
		log.startLog("---控制台请求路径:["+actionPath+"]---");
		return true;
	}
	
	/**
	 * 处理内容中的路径
	 * @param cnt         内容
	 * @param servletPath 触发动作路径
	 * @return            处理后的内容
	 * 2020年8月2日
	 * @author MBG
	 */
	private byte[] fixContentPath(byte[] cnt,String servletPath) {
		//构建返回值
		ByteArrayOutputStream bos     = new ByteArrayOutputStream();
		//路径字节流，（防止页面内容编写错误导致空指针，先构造一个实例）
		ByteArrayOutputStream pathBos = new ByteArrayOutputStream();
		int match       = 0;    //符合 0不匹配  1匹配开头*  2匹配第二个字符{
		String basePath = null; //当前动作根路径
		try {
			for(int i=0;i<cnt.length;i++) {
				if(cnt[i]=='*') {
					match = 1;
					continue;
				}
				if(match==1 && cnt[i]=='{') {
					if(cnt.length>i+1 && cnt[i+1]=='}') {
						i++;
						if(basePath==null) {
							basePath = servletPath.substring(0,servletPath.length()-actionPath.length()-1);
						}
						bos.write(basePath.getBytes());
						match = 0;
						continue;
					}
					match   = 2;
					pathBos = new ByteArrayOutputStream();
					continue;
				}
				if(match==2) {
					if(cnt[i]=='}') {
						bos.write(servletPath.getBytes());
						if(pathBos.size()>0) {
							bos.write('?');
							bos.write(des.encode(pathBos.toString()).getBytes());
						}
						match = 0;
					}else {
						pathBos.write(cnt[i]);
					}
					continue;
				}
				if(match==1) {
					match = 0;
					bos.write('*');
				}
				bos.write(cnt[i]);
			}
		}catch(Exception e) {
			e.printStackTrace();
		}
		return bos.toByteArray();
	}
    
	/**
	 * 加密路径信息
	 * 
	 * 拼装成当前请求路径级别（路径深度）/加密后的动作入口?加密后的路径
	 * 
	 * @param path        未加密路径
	 * @param servletPath 当前请求动作路径
	 * @return            加密后的路径信息
	 * 2020年8月4日
	 * @author MBG
	 */
	public String encPath(String path,String requestUrl) {
		if(path==null 
				|| path.length()<1 
				|| actionPath==null 
				|| actionPath.length()<1 
				|| requestUrl==null) {
			return path;
		}
		int point = requestUrl.lastIndexOf("/");
		if(point>-1) {
			requestUrl = requestUrl.substring(0,point);
		}
		requestUrl += "/"+actionPath+"?";
		try {
			return requestUrl+des.encode(path);
		}catch(Exception e) {}
		return path;
	}
	
	/**
	 * 解密路径信息
	 * @param path  加密路径信息
	 * @return      解密后的路径信息
	 * 2020年8月4日
	 * @author MBG
	 */
	public String decPath(String path) {
		if(path==null || path.length()<1 || actionPath==null || actionPath.length()<1) {
			return path;
		}
		try {
			return des.decode(path);
		}catch(Exception e) {}
		return path;
	}
	
    /**
     * 输出jar内部非文本文件
     * @param subPath       相对路径（不带/adm）
     * @param resp          返回对象
     * @param returnContent 是否返回文本内容
     * @throws Exception    异常
     * 2020年8月2日
     * @author MBG
     */
	private byte[] serveInsideFile(String subPath,IResponse resp,boolean returnContent) throws Exception {
		//Handle If-Modified-Since.
		//在输出404页面之前，已经设置好了404标识，结果到这里又变成了200，故屏蔽该处
		//并且显示包内部正常页面时，虽然没设置200标识，也可以正常显示
		//resp.setStatus(HttpServletResponse.SC_OK);
		resp.setHeader("Cache-Control", "no-cache");
		resp.setHeader("Pragma", "no-cache");
		resp.setDateHeader("Expires", 0); 
		resp.setHeader("Content-Type",HttpCall.getMime(SFilesUtil.getFileExtName(subPath)));
		if(notFromJar) {
			//从实际文件夹读取内置文本文件
			File outFile = new File(path("/adm"+subPath));
			if(!outFile.exists() || !outFile.isFile()) {
				resp.sendError(404);
				return null;
			}
			resp.setDateHeader("Last-modified",outFile.lastModified());
			if(returnContent) {
				return FileCopyTools.copyToByteArray(outFile);
			}
			resp.setHeader("Content-Length", Long.toString(outFile.length()));
			//输出
			try {
				FileCopyTools.copy(outFile,resp.getOutputStream());
			}catch(Exception e) {}
			return null;
		}
		//获取内部资源
		JarResourceVO resVO = loadInsideResource("/adm"+subPath);
		if(resVO==null) {
			resp.sendError(404);
			return null;
		}
		resp.setDateHeader("Last-modified", resVO.entrie.getTime());
		if(returnContent) {
			return FileCopyTools.copyToByteArray(resVO.jarFile.getInputStream(resVO.entrie));
		}
		resp.setHeader("Content-Length", Long.toString(resVO.entrie.getSize()));
		//输出
		try {
			FileCopyTools.copy(resVO.jarFile.getInputStream(resVO.entrie),resp.getOutputStream());
		}catch(Exception e) {}
		return null;
	}
	
	/**
	 * 获取内部文件字节数组
	 * @param path        内部文件相对路径
	 * @return            内部文件内容字节数组
	 * @throws Exception  异常
	 * 2020年9月8日
	 * @author MBG
	 */
	public byte[] loadFile(String path) throws Exception {
		//获取内部资源对象
		JarResourceVO vo = loadInsideResource(path);
		if(vo==null) {
			return null;
		}
		return FileCopyTools.copyToByteArray(vo.jarFile.getInputStream(vo.entrie));
	}
    

    /**
     * 尝试获取对应的静态资源
     * 
     * @param req 页面请求
     * @param res 页面反馈
     * @return true，找到对应的静态资源 2017年2月22日
     * @author MBG
     */
    public JarResourceVO loadInsideResource(String subPath) {
      if (subPath == null || subPath.length() < 1) {
        return null;
      }
      try {
        for (String ele : mappingKeyList) {
          if (subPath.startsWith(ele)) {
            JarResourceVO res = rl.getResourceVO(mappingMap.get(ele) + subPath);
            if (res == null) {
              warning("-------------Not Find The subPath:[" + subPath + "] In Native Jar File");
            }
            return res;
          }
        }
      } catch (Exception e) {
        e.printStackTrace();
      }
      return null;
    }
}
